package generator;

import generator.template.ExcludesGenerator;
import generator.template.ExcludingGenerator;
import generator.template.ExistsGenerator;
import generator.template.ForAllGenerator;
import generator.template.IncludesGenerator;
import generator.template.SelectGenerator;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import metamodel.Attribute;
import metamodel.MetamodelManager;
import metamodel.Method;
import metamodel.MyClass;
import semantic.analyzer.GodAnalyzer;
import semantic.analyzer.StereotypeAnalyzer.StereotypeTypeEnum;
import semantic.model.ExpressionNode;
import semantic.model.PrimaryExpressionNode;

public class CodeGenerator {

	public static HashMap<String,Code> codigos = new HashMap<String,Code>();
	
	private static List<StringBuffer> metodos = new ArrayList<StringBuffer>();
	
	private static int excludesCounter = 0;
	private static int excludingCounter = 0;
	private static int existsCounter = 0;
	private static int forAllCounter = 0;
	private static int includesCounter = 0;
	private static int selectCounter = 0;
	
	public static void createClassesCode(MetamodelManager tabelaSemantica) {
		for (String key : tabelaSemantica.getClasses().keySet()) {
			
			StringBuffer buffer = new StringBuffer();
			
			MyClass klass = tabelaSemantica.getClasses().get(key);
			buffer.append("package " + klass.getPackageName() + " {\n");
			if (klass.getName().equalsIgnoreCase(klass.getType())){
				buffer.append("\tpublic class "+klass.getName()+" {\n\n");
				
				for (String att : klass.getAttributes().keySet()) {
					Attribute attribute = klass.getAttribute(att);
					buffer.append("\t\t" + attribute.getAccessor() + " var " + attribute.getName()+":" + translateAttType(attribute.getType()) + ";\n\n");			
				}
				for (String metodo : klass.getMethods().keySet()) {
					Method method = klass.getMethod(metodo);
					String returnType = translateAttType(method.getReturnType());
					String parameters = "";
					for (Attribute parameter : method.getParameters()) {
						parameters += parameter.getName() + ":" + translateAttType(parameter.getType()) + ", ";
					}
					if (parameters.contains(", ")) parameters = parameters.substring(0, parameters.length() - 2);
					buffer.append("\t\t" + method.getAccessor() + " function " + method.getName() +"(" + parameters + "):"+ returnType +"{" + generateReturn(returnType)+ "}\n\n");
				}
			} else {
				buffer.append("\tpublic class "+klass.getName()+" extends "+ klass.getType()+ " {\n\n");
				MyClass father	= tabelaSemantica.getClasses().get(klass.getType());
				MyClass son 	= tabelaSemantica.getClasses().get(klass.getName());				
				addSonAttributes(buffer, father, son);				
				addSonMethods(buffer, father, son);
			}
			buffer.append("\t}\n}");
			Code codigo = new Code();
			codigo.insertAttributesAndSignatures(buffer.toString());
			codigos.put(klass.getName(), codigo);
		}
		
		for(String className: codigos.keySet()){
			if(!className.contains("Set")){
				Code codigo = codigos.get(className);
				try {
					BufferedWriter out = new BufferedWriter(new FileWriter("output/"+className+".as"));
					String outText = codigo.toString();
					out.write(outText);
					out.close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
//			System.out.println(codigo.toString());
		}
	}

	private static void addSonMethods(StringBuffer buffer, MyClass father, MyClass son) {
		HashMap<String,Method> fMethods = father.getMethods();
		HashMap<String,Method> sMethods = son.getMethods();
		List<Method> methods = new ArrayList<Method>();
		boolean found;
		for (Method sonMethod : sMethods.values()) {
			found = false;
			for (Method fatherMethod : fMethods.values()) {
				if(sonMethod.getName().equalsIgnoreCase(fatherMethod.getName())){
					found = true;
					break;
				}						
			}
			if(!found){methods.add(sonMethod);}
		}
		for (Method method : methods) {
			String returnType = translateAttType(method.getReturnType());
			String parameters = "";
			for (Attribute parameter : method.getParameters()) {
				parameters += parameter.getName() + ":" + translateAttType(parameter.getType()) + ", ";
			}
			System.out.println(method.getName());
			buffer.append("\t\t" + method.getAccessor() + " function " + method.getName() +"():"+ returnType +"{" +generateReturn(returnType)+ "}\n\n");
		}
	}

	private static void addSonAttributes(StringBuffer buffer, MyClass father, MyClass son) {
		HashMap<String,Attribute> fAttrs = father.getAttributes();
		HashMap<String,Attribute> sAttrs = son.getAttributes();
		List<Attribute> attributes = new ArrayList<Attribute>();
		boolean found;
		for (Attribute sonAttribute : sAttrs.values()) {
			found = false;
			for (Attribute fatherAttribute : fAttrs.values()) {
				if(sonAttribute.getName().equalsIgnoreCase(fatherAttribute.getName())){
					found = true;
					break;
				}						
			}
			if(!found){attributes.add(sonAttribute);}
		}
		for (Attribute attribute : attributes) {
			buffer.append("\t\t" + attribute.getAccessor() + " var " + attribute.getName()+":" + translateAttType(attribute.getType()) + ";\n\n");			
		}
	}
	
	private static String generateReturn(String returnType) {
		String RETURN = "return ";
		if(returnType.equalsIgnoreCase("boolean")){ RETURN += "false;";}
		else if(returnType.equalsIgnoreCase("number")){ RETURN += "0.0;";}
		else if(returnType.equalsIgnoreCase("string")){ RETURN += "\"\";";}
		else if(returnType.equalsIgnoreCase("int")){ RETURN += "0;";}
		else if(returnType.equalsIgnoreCase("void")){ RETURN = "";}
		else { RETURN += "new "+returnType+"();";}
		return RETURN;
	}

	private static String translateAttType(String oclType) {
		return TranslatorTypes.translateType(oclType);		
	}

	public static String getCode(List<PrimaryExpressionNode> nodes) throws Exception{
		StringBuffer buffer = new StringBuffer();
		for (int i = 0 ; i < nodes.size() ; i++) {
			PrimaryExpressionNode node = nodes.get(i);
			if(node.getValue().equalsIgnoreCase("self")){
				buffer.append("this");
			} else {
				if(i!=0){ buffer.append("."); }
				if (node.getValue().equalsIgnoreCase("size()")){
					buffer.append("length");
				}else if(node.getValue().startsWith("select(")){
					doSelectStuff(nodes, buffer, i, node);
				}else if(node.getValue().startsWith("exists(")){
					doExistsStuff(nodes, buffer, i, node);
				}else if(node.getValue().startsWith("includes(")){
					doIncludesStuff(nodes, buffer, i, node); 
				}else if(node.getValue().startsWith("excludes(")){
					doExcludesStuff(nodes, buffer, i, node); 
				}else if(node.getValue().startsWith("including(")){
					String element = node.getValue().substring(10, node.getValue().length()-1);
					buffer.append("concat("+element+")");
				}else if(node.getValue().startsWith("excluding(")){
					doExcludingStuff(nodes, buffer, i, node); 
				}else if(node.getValue().startsWith("forAll(")){
					doForAllStuff(nodes, buffer, i, node); 
				}else{
					buffer.append(node.getValue());
				}				
			}
		}
		return buffer.toString();
	}

	private static void doForAllStuff(List<PrimaryExpressionNode> nodes, StringBuffer buffer, int i, PrimaryExpressionNode node) throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String condition = node.getValue().substring(7, node.getValue().length()-1);
		createForAll(path,type,condition);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "forAll"+type+""+(forAllCounter++)+"("+path+")");
	}

	private static void createForAll(String path, String type, String condition) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(ForAllGenerator.generate(path, type, condition,forAllCounter));
		metodos.add(metodo);
	}

	private static void doExcludingStuff(List<PrimaryExpressionNode> nodes, StringBuffer buffer, int i, PrimaryExpressionNode node) throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String element = node.getValue().substring(10, node.getValue().length()-1);
		createExcluding(path,type,element);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "excluding"+type+""+(excludingCounter++)+"("+path+","+element+")");
	}

	private static void createExcluding(String path, String type, String element) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(ExcludingGenerator.generate(path, type, element,excludingCounter));
		metodos.add(metodo);		
	}

	private static void doExcludesStuff(List<PrimaryExpressionNode> nodes, StringBuffer buffer, int i, PrimaryExpressionNode node) throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String element = node.getValue().substring(9, node.getValue().length()-1);
		createExcludes(path,type,element);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "excludes"+type+""+(excludesCounter++)+"("+path+")");		
	}
	
	private static void createExcludes(String path, String type, String element) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(ExcludesGenerator.generate(path, type, element,excludesCounter));
		metodos.add(metodo);
	}

	private static void doIncludesStuff(List<PrimaryExpressionNode> nodes, StringBuffer buffer, int i, PrimaryExpressionNode node) throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String element = node.getValue().substring(9, node.getValue().length()-1);
		createIncludes(path,type,element);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "includes"+type+""+(includesCounter++)+"("+path+")");
	}
	
	private static void createIncludes(String path, String type, String element) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(IncludesGenerator.generate(path, type, element,includesCounter));
		metodos.add(metodo);		
	}

	private static void doExistsStuff(List<PrimaryExpressionNode> nodes, StringBuffer buffer, int i, PrimaryExpressionNode node) throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String condition = node.getValue().substring(7, node.getValue().length()-1);
		createExists(path,type,condition);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "exists"+type+""+(existsCounter++)+"("+path+")");
	}

	private static void createExists(String path, String type, String condition) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(ExistsGenerator.generate(path, type, condition,existsCounter));
		metodos.add(metodo);		
	}

	private static void doSelectStuff(List<PrimaryExpressionNode> nodes,StringBuffer buffer, int i, PrimaryExpressionNode node)	throws Exception {
		String path = getPathSelect(nodes, i - 1);
		String type = getTypeSelect(nodes, i - 1);
		String condition = node.getValue().substring(7, node.getValue().length()-1);
		createSelect(path,type,condition);
		int position = buffer.lastIndexOf(path);
		buffer.replace(position, path.length()+1, "select"+type+""+(selectCounter++)+"("+path+")");
	}
	
	private static void createSelect(String path, String type, String condition) {
		StringBuffer metodo = new StringBuffer();
		metodo.append(SelectGenerator.generate(path, type, condition,selectCounter));
		metodos.add(metodo);
	}

	private static String getTypeSelect(List<PrimaryExpressionNode> nodes, int i) {
		List<PrimaryExpressionNode> nodes2 = new ArrayList<PrimaryExpressionNode>();
		PrimaryExpressionNode node;
		for (int j = i; true; j--) {
			node = nodes.get(j);
			if (node.getPropertyCallNode() != null && node.getPropertyCallNode().getAccessor() == null) {
				nodes2.add(0, node);
				break;
			}
			nodes2.add(0, node);
		}
		ExpressionNode node2 = GodAnalyzer.check(nodes2);
		return node2.getType().substring(4, node2.getType().length()-1);
	}

	private static String getPathSelect(List<PrimaryExpressionNode> nodes, int i) {
		String path = "";
		PrimaryExpressionNode node;
		for (int j = i; true; j--) {
			node = nodes.get(j);
			if (node.getPropertyCallNode() != null && node.getPropertyCallNode().getAccessor() == null) {
				path = node.getPropertyCallNode().getValue() + "." + path;
				path = path.substring(0, path.length()-1);
				break;
			}
			path = node.getPropertyCallNode().getValue() + "." + path;
		}
		return path;
	}

	public static String getIfCode(ExpressionNode exp1, ExpressionNode exp2, ExpressionNode exp3){
		return "if(" + exp1.getValue() + "){\n"+
				"\t" + exp2.getValue()+ ";\n"+
				"}else{\n"+
				"\t"+ exp3.getValue()+ ";\n"+
				"}\n";
	}
	
	public static void insertCodeInClass(String klassName, List<UglyObject> evils){
		String className = klassName.split("::")[0];
		Code codigo = codigos.get(className);
		
		for (UglyObject evilObject : evils) {			
			if (codigo.firstTime){
				codigo.insertTemplate(klassName.split("::")[1]);
			}
			if (evilObject.getType().equals(StereotypeTypeEnum.PRE)){
				codigo.insertCheckPre(evilObject.getOcl());
			}
			if(evilObject.getType().equals(StereotypeTypeEnum.POS)){
				codigo.insertExecutePos(evilObject.getOcl());	
			}
		}

		codigo.insertReturn();
		codigo.preAndPostfixer();
		codigo.insertAuxMethods(metodos);
		
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter("output/"+className+".as"));
			String outText = codigo.toString();
			out.write(outText);
			out.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
//		System.out.println(codigo.toString());
	}
	
}
